package com.ynet.middleground.contract.policy;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import org.springframework.stereotype.Component;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.policy.RenderPolicy;
import com.deepoove.poi.render.compute.RenderDataCompute;
import com.deepoove.poi.render.processor.DocumentProcessor;
import com.deepoove.poi.resolver.TemplateResolver;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.ReflectionUtils;
import com.deepoove.poi.util.TableTools;
import com.ynet.core.codec.mark.util.CollectionUtil;
import com.ynet.core.log.IfpLogger;
import com.ynet.middleground.contract.bean.MergeCellTableInfo;
import com.ynet.middleground.contract.bean.MergeCellVerticallyBean;
import com.ynet.middleground.contract.bean.MergeCellsHorizonalBean;

/**
 * @author liulx
 * @description 循环表格渲染策略
 * 
 *              <pre>
 *     该类主要功能为：渲染表格，包括合并单元格；
 *     其中合并单元格有两种合并策略，分别为：具体坐标合并、相同单元格内容合并；
 *     且两者同时仅可使用 1 个，如果均使用，则使用坐标合并方式。
 *
 *    <h2>前提知识</h2>：
 *      1. 表格有列、行；
 *      2. 合并行为分为 「垂直合并」 和 「水平合并」
 *      3. 垂直合并: 第几列的第几行到第几行合并；
 *      4. 水平合并: 第几行的第几列到第几列合并;
 *
 *      <h3>表格示例</h3>：
 *      以下为 5 * 6 的表格（不包括表头）
 *
 *   列号 -->         1          2         3           4           5         6
 *                +────────+──────────+───────────+────────────+───────+────────────+
 *   行号         |  姓名   | 公司部门 |  缺勤/周  |  缺勤统计  | 技能  | 奇技       |
 *    ↓           +────────+──────────+───────────+────────────+───────+────────────+
 *    1           | 宝儿姐  ║ 哪都通   ║   0　　　 |     0     | 不详  | 不详　 　 　|
 *    2           | 张楚岚  ║ 哪都通   ║   1　　　 |     1     | 雷法  | 金光咒　 　 |
 *    3           | 张之维  | 天师府   |   0　　　 |     0     | 太极  | 风后奇门　  |
 *    4           | 张灵玉  | 哪都通   |   0　　　 |     0     | 雷法  | 天师度　 　 |
 *    5           | 徐三儿  | 哪都通   |   2　　　 |     4     | 不详  | 不详　 　 　|
 *                +────────+──────────+───────────+────────────+───────+────────────+
 *
 *    <h2>合并策略解释</h2>：
 *     1. <b>坐标合并方式简述</b>：参考上述垂直合并和水平合并解释，即给出对应列号、行号，实现合并！
 *       eg: 参考上述表格示例，第 2 列，第 1~2 行合并，即，将 "宝儿姐" 和 "张楚岚" 的公司部门「哪都通」合并成 1 个。
 *       坐标参数可参考 {@link MergeCellTableInfo}
 *
 *     2. <b>相同单元格内容合并方式简述</b>：相同单元格内容合并方式其实是通过给需要合并的单元格内容添加上合并的标记，然后
 *     通过解析单元格内容数据，从而获得对应的要合并的列号、行号 等信息，最终转换为坐标合并方式。
 *
 *     如下表, 给需要上下（垂直）合并的内容添加 "#m.tag.row#" 样的后缀，给需要左右（水平）合并的添加 "#m.tag.col#" 的前缀。
 *
 *   列号 -->         1          2                    3                   4
 *                +────────+──────────────────────+──────────────────+───────────────────────+
 *   行号         |  姓名   | 公司部门             | 技能             |   奇技              |
 *    ↓           +────────+──────────────────────+──────────────────+───────────────────────+
 *    1           | 宝儿姐  ║ 哪都通#m.tag.row#   ║ 不详             | 不详　 　            |
 *    2           | 张楚岚  ║ 哪都通#m.tag.row#   ║ 雷法             | 金光咒　             |
 *    3           | 张之维  | 天师府              | 太极             | 风后奇门　           |
 *    4           | 张灵玉  | 哪都通              | 雷法             | 天师度　             |
 *    5           | 徐三儿  | 哪都通              ║ #m.tag.col#不详  | #m.tag.col#不详　 　 ║
 *                +────────+──────────────────────+──────────────────+───────────────────────+
 *
 *   <h3>相同单元格内容合并逻辑方案</h3>：
 *     详见本文 {@link HackLoopTableRenderPolicy#render}
 *
 *   <h2>注意：暂时不支持某个单元格既有垂直合并又有水平合并的情况</h2>
 *              </pre>
 *
 * @date 2020-04-01 15:42
 * @modifyDate 2021-09-17 23:20:13
 */
@Component
public class HackLoopTableRenderPolicy implements RenderPolicy {

    /**
     * 垂直合并数据的标记后缀
     */
    public static final String MERGE_ROW_KEY = "#m.tag.row#";

    /**
     * 水平合并数据的标记前缀
     */
    public static final String MERGE_COL_KEY = "#m.tag.col#";

    private String prefix;
    private String suffix;

    public HackLoopTableRenderPolicy() {
        this("[", "]");
    }

    public HackLoopTableRenderPolicy(String prefix, String suffix) {
        this.prefix = prefix;
        this.suffix = suffix;
    }

    @Override
    public void render(ElementTemplate eleTemplate, Object allData, XWPFTemplate template) {
        RunTemplate runTemplate = (RunTemplate)eleTemplate;
        XWPFRun run = runTemplate.getRun();
        try {
            if (!TableTools.isInsideTable(run)) {
                throw new IllegalStateException(
                    "The template tag " + runTemplate.getSource() + " must be inside a table");
            }
            Map<String, Object> allDataMap = (Map<String, Object>)allData;
            Object data = allDataMap.get("renderParamData");

            XWPFTableCell tagCell = (XWPFTableCell)((XWPFParagraph)run.getParent()).getBody();
            XWPFTableRow tagRow = tagCell.getTableRow();
            XWPFTable table = tagCell.getTableRow().getTable();

            // 表格单元格的参数及其对应的列号
            LinkedHashMap<String, Integer> cellIndex = new LinkedHashMap<>();

            int templateRowIndex = getRowIndex(table, tagRow) + 1;
            if (null != data && data instanceof Iterable) {
                Iterator<?> iterator = ((Iterable<?>)data).iterator();
                XWPFTableRow templateRow = table.getRow(templateRowIndex);
                int insertPosition = templateRowIndex;

                TemplateResolver resolver = new TemplateResolver(template.getConfig().clone(prefix, suffix));
                // 获取单元格参数及对应的列号
                List<XWPFTableCell> tableCells = templateRow.getTableCells();
                for (int i = 0; i < tableCells.size(); i++) {
                    List<IBodyElement> bodyElements = tableCells.get(i).getBodyElements();
                    if (CollectionUtil.isNotEmpty(bodyElements)) {
                        XWPFParagraph paragraph = (XWPFParagraph)bodyElements.get(0);
                        if (!paragraph.getRuns().isEmpty()) {
                            StringBuilder builder = new StringBuilder();
                            paragraph.getRuns().forEach(xwpfRun -> {
                                builder.append(StringUtils.strip(xwpfRun.text(), "[]"));
                            });
                            // cellIndex.put(StringUtils.strip(paragraph.getRuns().get(0).text(), "[]"), i + 1);
                            cellIndex.put(builder.toString(), i + 1);
                        }
                    }
                }
                IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "单元格参数列号信息: {} ", cellIndex);

                // 垂直合并的单元格集合信息，key 为 行号，value 为当前行需要 水平合并的 集合列表
                LinkedHashMap<Integer, LinkedHashMap<Integer, Integer>> cellInfoMergeVeList = new LinkedHashMap<>();
                // 水平合并的单元格集合信息，key 为 行号，value 为当前行需要 水平合并的 集合列表
                LinkedHashMap<Integer, LinkedHashMap<Integer, Integer>> cellInfoMergeHeList = new LinkedHashMap<>();
                // 前一行的数据
                LinkedHashMap<String, String> prevRowData = null;
                // 前一行数据行号
                Integer prevRowPosition = null;

                while (iterator.hasNext()) {
                    insertPosition = templateRowIndex++;
                    XWPFTableRow nextRow = table.insertNewTableRow(insertPosition);
                    setTableRow(table, templateRow, insertPosition);

                    // double set row
                    XmlCursor newCursor = templateRow.getCtRow().newCursor();
                    newCursor.toPrevSibling();
                    XmlObject object = newCursor.getObject();
                    nextRow = new XWPFTableRow((CTRow)object, table);
                    setTableRow(table, nextRow, insertPosition);

                    // Object cellContent = iterator.next();

                    // 当前行要填充渲染的各列数据，后续内容解析比较后，会去除合并标记的字符串，作为渲染时使用
                    LinkedHashMap cellContent = (LinkedHashMap)iterator.next();

                    // 处理待合并数据 ｜ start
                    // 重新复制一份当前行需要渲染的数据，因为带着合并的标记字符串，所以作为解析单元格内容使用
                    LinkedHashMap<String, String> cellContentMap = new LinkedHashMap<>(cellContent);
                    IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "单元格原始信息: {}",
                        cellContentMap.toString());

                    // 当前行需要水平合并的列表， key 为 截止列号，value 为 起始列号
                    LinkedHashMap<Integer, Integer> mergeHeForCurrentRow = new LinkedHashMap<>();

                    // 当前行，前一个单元格的参数名
                    String prevName = null;
                    // 当前行，前一个单元格的参数值
                    String prevValue = null;

                    // 循环当前行的各项参数信息
                    Set<Map.Entry<String, String>> entries = cellContentMap.entrySet();
                    for (Map.Entry<String, String> entry : entries) {
                        String name = entry.getKey();
                        String value = entry.getValue();

                        // 去除所有合并标记，并更新回列表，以供渲染时使用
                        cellContent.put(name, value.replaceAll(MERGE_ROW_KEY, "").replaceAll(MERGE_COL_KEY, ""));

                        // 判断单元格的值是不是带有 水平合并 的 前缀
                        if (value.startsWith(MERGE_COL_KEY)) {
                            // 记录「当前行」本次有水平合并标记的数据，key 为截止列号 、value 为起始列号
                            // eg: 3,3 即，代表当前行要从 第 3 列合并到 第 3 列（在后续合并的时候会过滤起始列号与截止列号一样的数据，这种数据不合并）
                            mergeHeForCurrentRow.put(cellIndex.get(name), cellIndex.get(name));

                            // 先去除单元格的垂直合并标记，防止数据同时有水平合并、垂直合并两种标记导致无法找出相同数据
                            // 再判断当前单元格数据是否与前一个单元格数据一致，若为一致，则说明当前单元格需要与前一个单元格水平合并
                            if (prevValue != null && value.replaceAll(MERGE_ROW_KEY, "")
                                .equals(prevValue.replaceAll(MERGE_ROW_KEY, ""))) {
                                // 取出 前一个单元格的需要合并的 起始列号
                                Integer preIndex = mergeHeForCurrentRow.get(cellIndex.get(prevName));
                                // 以当前列号作为截止列号，前一个起始列号，添加到集合中
                                mergeHeForCurrentRow.put(cellIndex.get(name), preIndex);
                                // 再删除前一个记录的合并信息
                                mergeHeForCurrentRow.remove(cellIndex.get(prevName));
                            }
                        }
                        // 判断单元格的值是不是带有 垂直合并 的 后缀
                        if (value.endsWith(MERGE_ROW_KEY)) {
                            // 根据列名取出本次需要跨行合并数据，没有则初始化
                            LinkedHashMap<Integer, Integer> mergeVeForCurrentCol =
                                cellInfoMergeVeList.get(cellIndex.get(name));
                            if (mergeVeForCurrentCol == null) {
                                mergeVeForCurrentCol = new LinkedHashMap<>();
                            }
                            // 记录 「当前列」本次有垂直合并标记的数据，key 为截止行号 、value 为起始行号
                            mergeVeForCurrentCol.put(insertPosition, insertPosition);
                            // 先去除单元格的水平合并标记，防止数据同时有水平合并、垂直合并两种标记导致无法找出相同数据
                            // 再判断当前单元格数据是否与上一行数据该列单元格数据一致，若为一致，则说明当前单元格需要与上一个单元格垂直合并
                            if (prevRowData != null && prevRowData.get(name).replaceAll(MERGE_COL_KEY, "")
                                .equals(value.replaceAll(MERGE_COL_KEY, ""))) {
                                // 取出当前列的垂直合并的集合
                                mergeVeForCurrentCol = cellInfoMergeVeList.get(cellIndex.get(name));
                                // 从当前列处置合并的集中取出上一行合并的开始行号作为本次合并开始行号，并以本次行号作为合并截止行号
                                mergeVeForCurrentCol.put(insertPosition, mergeVeForCurrentCol.get(prevRowPosition));
                                // 移除上一个记录的合并信息
                                mergeVeForCurrentCol.remove(prevRowPosition);
                            }
                            // 记录或更新当前列的垂直合并集合
                            cellInfoMergeVeList.put(cellIndex.get(name), mergeVeForCurrentCol);
                        }
                        prevName = name;
                        prevValue = value;
                    }

                    // 记录当前行需要水平合并的列表
                    cellInfoMergeHeList.put(insertPosition, mergeHeForCurrentRow);
                    prevRowData = cellContentMap;
                    prevRowPosition = insertPosition;
                    // 处理待合并数据 ｜ end

                    IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "单元格去除合并标识后信息: {}",
                        cellContent.toString());
                    // end
                    RenderDataCompute dataCompute =
                        template.getConfig().getRenderDataComputeFactory().newCompute(cellContent);
                    List<XWPFTableCell> cells = nextRow.getTableCells();
                    cells.forEach(cell -> {
                        List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
                        new DocumentProcessor(template, resolver, dataCompute).process(templates);
                    });
                }

                // 合并单元格
                executeMergeCells(allDataMap, table, cellInfoMergeVeList, cellInfoMergeHeList);
            }

            run.setText("", 0);
            table.removeRow(templateRowIndex);

        } catch (Exception e) {
            throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
        }
    }

    /**
     * 执行合并单元格
     * 
     * @param allDataMap 传递过来的全部表格数据
     * @param table 表格对象
     * @param cellInfoMergeVeList 垂直合并单元格的数据
     * @param cellInfoMergeHeList 水平合并单元格的数据
     * @author liulx
     */
    private void executeMergeCells(Map<String, Object> allDataMap, XWPFTable table,
        LinkedHashMap<Integer, LinkedHashMap<Integer, Integer>> cellInfoMergeVeList,
        LinkedHashMap<Integer, LinkedHashMap<Integer, Integer>> cellInfoMergeHeList) {
        Object mergeCellsData = allDataMap.get("mergeCellsData");
        // 根据坐标合并单元格
        if (null != mergeCellsData && mergeCellsData instanceof MergeCellTableInfo) {
            List<MergeCellsHorizonalBean> mergeCellsHorizonal =
                ((MergeCellTableInfo)mergeCellsData).getMergeCellsHorizonal();
            List<MergeCellVerticallyBean> mergeCellsVertically =
                ((MergeCellTableInfo)mergeCellsData).getMergeCellsVertically();

            mergeCellsVertically.forEach(cell -> {
                TableTools.mergeCellsVertically(table, cell.getCol() - 1, cell.getFromRow(), cell.getToRow());
            });

            mergeCellsHorizonal.forEach(cell -> {
                TableTools.mergeCellsHorizonal(table, cell.getRow(), cell.getFromCol() - 1, cell.getToCol() - 1);
            });
        } else {
            // 根据内容合并单元格
            // 垂直合并
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "垂直合并 cellInfoMergeVeList: {} ",
                cellInfoMergeVeList.toString());

            cellInfoMergeVeList.forEach((col, merge) -> {
                merge.forEach((toRow, fromRow) -> {
                    if (toRow != null && fromRow != null && !toRow.equals(fromRow)) {
                        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(),
                            "合并单元格信息: {} 列，从 {} - {} 行【垂直】合并 ", col, fromRow, toRow);
                        TableTools.mergeCellsVertically(table, col - 1, fromRow, toRow);
                    }
                });
            });

            // 水平合并
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "水平合并 cellInfoMergeHeList: {} ",
                cellInfoMergeHeList.toString());
            cellInfoMergeHeList.forEach((rowIndex, merge) -> {
                // 已经合并的单元格的位移差
                AtomicInteger moveSize = new AtomicInteger(0);
                merge.forEach((toCol, fromCol) -> {
                    if (toCol != null && fromCol != null && !toCol.equals(fromCol)) {
                        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(),
                            "合并单元格信息: {} 行，从 {} - {} 列【水平】合并 ", rowIndex, fromCol, toCol);
                        TableTools.mergeCellsHorizonal(table, rowIndex, (fromCol - 1 - moveSize.get()),
                            (toCol - 1 - moveSize.get()));
                        moveSize.set(moveSize.get() + (toCol - fromCol));
                    }
                });
            });
        }
    }

    @SuppressWarnings("unchecked")
    private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
        List<XWPFTableRow> rows = (List<XWPFTableRow>)ReflectionUtils.getValue("tableRows", table);
        rows.set(pos, templateRow);
        table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
    }

    private int getRowIndex(XWPFTable table, XWPFTableRow row) {
        List<XWPFTableRow> rows = table.getRows();
        return rows.indexOf(row);
    }

}
